package com.pig.sentinel;

import com.pig.sentinel.annotions.Sentinel;
import com.pig.sentinel.aop.CurrentObj;
import com.pig.sentinel.aop.ObjThreadLocal;
import com.pig.sentinel.context.DegradeContext;
import com.pig.sentinel.context.FlowContext;
import com.pig.sentinel.context.MethodSentinelContext;
import com.pig.sentinel.enums.ProcessTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.pig.sentinel.SentinalConst.QPS_COMPUTE_INTERVAL;

/**
 * @author lizhiwei
 */
@Component
@Slf4j
public class SentinelResourceManager {
    /**
     * 用来计算qps的线程
     */
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    /**
     * 存储所有的资源
     */
    private Map<String, MethodSentinelContext> all = new ConcurrentHashMap<>();

    @PostConstruct
    public void startComputingQps() {
        executorService.execute(() -> {
            while (true) {
                for (Map.Entry<String, MethodSentinelContext> entry : all.entrySet()) {
                    MethodSentinelContext value = entry.getValue();
                    FlowContext flowContext = value.getFlowContext();
                    flowContext.addQpsQueue();
                    DegradeContext degradeContext = value.getDegradeContext();
                    degradeContext.restWindowStartTime();
                }
                try {
                    Thread.sleep(QPS_COMPUTE_INTERVAL);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 分析方法的Sentinel注解参数
     *
     * @param target
     * @param method
     */
    public void analyzeMethod(Object target, Method method) {
        Sentinel declaredAnnotation = method.getDeclaredAnnotation(Sentinel.class);
        if (declaredAnnotation == null) {
            return;
        }
        String resourceName = declaredAnnotation.resourceName();
        if (all.containsKey(resourceName)) {
            return;
        }
        String exceptionHandler = declaredAnnotation.exceptionHandler();
        try {
            Method exceptionHandlerMethod = target.getClass().getDeclaredMethod(exceptionHandler, method.getParameterTypes());
            MethodSentinelContext context = new MethodSentinelContext(exceptionHandlerMethod, declaredAnnotation.processType());
            all.put(resourceName, context);
        } catch (NoSuchMethodException e) {
            log.error("not found exceptionHandler method");
        }
    }

    /**
     * 当前执行的方法，是否正在降级的窗口期
     *
     * @param method
     */
    public boolean isDegradeing(Method method) {
        String resourceName = getResourceName(method);
        MethodSentinelContext context = all.get(resourceName);
        if (context == null) {
            return false;
        }
        if (!context.support(ProcessTypeEnum.DEGRADE)) {
            return false;
        }
        return context.getDegradeContext().isWindowTime();
    }

    /**
     * 方法异常时，增加异常异常次数
     *
     * @param method
     */
    public void incExceptionTimes(Method method) {
        String resourceName = getResourceName(method);
        MethodSentinelContext context = all.get(resourceName);
        if (context == null) {
            return;
        }
        if (!context.support(ProcessTypeEnum.DEGRADE)) {
            return;
        }
        context.getDegradeContext().incExceptionTimes();
    }

    /**
     * 重置异常次数
     *
     * @param method
     */
    private void resetExceptionTimes(Method method) {
        String resourceName = getResourceName(method);
        MethodSentinelContext context = all.get(resourceName);
        if (context == null) {
            return;
        }
        if (!context.support(ProcessTypeEnum.DEGRADE)) {
            return;
        }
        context.getDegradeContext().restExceptionTimes();
    }

    /**
     * 方法执行成功，增加一次执行次数，重置异常次数
     *
     * @param method
     */
    public void execSuccess(Method method) {
        resetExceptionTimes(method);
        incExecTimes(method);
    }

    /**
     * 方法执行次数
     *
     * @param method
     */
    private void incExecTimes(Method method) {
        String resourceName = getResourceName(method);
        MethodSentinelContext context = all.get(resourceName);
        if (context == null) {
            return;
        }
        if (!context.support(ProcessTypeEnum.FLOW)) {
            return;
        }
        context.getFlowContext().incExeTimes();
    }
    /**
     * 限流或降级时要执行的方法
     *
     * @param method
     * @return
     */
    public Object execExceptionHandler(Method method) {
        String resourceName = getResourceName(method);
        if (StringUtils.isEmpty(resourceName)) {
            return null;
        }
        MethodSentinelContext context = all.get(resourceName);
        if (context == null) {
            return null;
        }
        CurrentObj currentObj = ObjThreadLocal.get();
        try {
            return context.getExceptionHandler().invoke(currentObj.getTarget(), currentObj.getArgs());
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 是否正在限流中
     *
     * @param method
     * @return
     */
    public boolean isFlowing(Method method) {
        String resourceName = getResourceName(method);
        MethodSentinelContext context = all.get(resourceName);
        if (context == null) {
            return false;
        }
        if (!context.support(ProcessTypeEnum.FLOW)) {
            return false;
        }

        long currentQpc = context.getFlowContext().getCurrentQpc();
        if (currentQpc > SentinalConst.MAX_QPS) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 从方法上分析出注解定义的资源名
     *
     * @param method
     * @return
     */
    public String getResourceName(Method method) {
        Sentinel declaredAnnotation = method.getDeclaredAnnotation(Sentinel.class);
        if (declaredAnnotation == null) {
            return null;
        }
        return declaredAnnotation.resourceName();
    }
}